/*
 * Copyright 2010 Aekold Helbrass
 * 
 * This file is part of RosterComposer.
 * 
 * RosterComposer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * RosterComposer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with RosterComposer.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.heresylabs.rostercomposer.roster;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.heresylabs.rostercomposer.units.Category;
import org.heresylabs.rostercomposer.units.Option;
import org.heresylabs.rostercomposer.units.Unit;

/**
 *
 * @author Aekold Helbrass <Helbrass@gmail.com>
 */
public class RosterEntry implements Comparable<RosterEntry>, java.io.Serializable {

    transient private static final Integer ONE = Integer.valueOf(1);
    transient private List<EntryNumberListener> numberListeners;
    transient private List<EntryOptionsListener> optionsListeners;
    private Unit unit;
    private Category category;
    private int number;
    transient private int cost = -1;
    private Set<String> selectedOptionNames;
    private Map<Option, Integer> selectedOptions;
    transient private String optionsSummary;

    public RosterEntry(Unit unit, Category category) {
        this.unit = unit;
        this.category = category;
        number = unit.getMinNumber();
    }

    public int calculateCost() {
        cost = 0;

        if (unit.getBaseCost() < 1) {
            cost += number * unit.getCost();
        }
        else {
            cost += unit.getBaseCost();
            cost += (number - unit.getMinNumber()) * unit.getCost();
        }

        if (selectedOptions != null && selectedOptions.size() > 0) {
            StringBuilder sb = new StringBuilder();
            Iterator<Entry<Option, Integer>> iterator = selectedOptions.entrySet().iterator();
            while (iterator.hasNext()) {
                Entry<Option, Integer> e = iterator.next();
                Option option = e.getKey();
                int optionCost = option.getCost();
                int optionsNumber;
                Integer value = e.getValue();
                if (value == null) {
                    optionsNumber = 1;
                }
                else {
                    optionsNumber = value.intValue();
                }
                if (option.isForEach()) {
                    optionsNumber = number;
                }
                cost += optionCost * optionsNumber;

                if (option.isFree() && !option.isLeaf()) {
                    continue;
                }
                if (optionsNumber > 1) {
                    sb.append(optionsNumber).append('*');
                }
                sb.append(option.getShortName());
                sb.append("; ");
            }
            optionsSummary = sb.toString();
        }
        else {
            optionsSummary = null;
        }

        return cost;
    }

    private void checkOptionHolders() {
        if (selectedOptions == null) {
            selectedOptions = new LinkedHashMap<Option, Integer>();
            selectedOptionNames = new HashSet<String>();
        }
    }

    public boolean hasOption(Option option) {
        if (selectedOptions == null || selectedOptions.isEmpty()) {
            return false;
        }
        return selectedOptions.containsKey(option);
    }

    public boolean hasOption(String optionName) {
        if (selectedOptionNames == null || selectedOptionNames.isEmpty()) {
            return false;
        }
        return selectedOptionNames.contains(optionName);
    }

    public void addOption(Option option) {
        checkOptionHolders();
        selectedOptionNames.add(option.getName());
        // checking do we have this option already:
        Integer n = selectedOptions.get(option);
        if (n == null || n.intValue() == 0) {
            // no we don't, putting 1 option:
            selectedOptions.put(option, ONE);
        }
        else {
            int t = n.intValue();
            t++;
            selectedOptions.put(option, t);
        }
        fireOptionChange(option, true);
    }

    public void removeOption(Option option) {
        if (selectedOptions == null || selectedOptions.isEmpty()) {
            return;
        }
        Integer n = selectedOptions.get(option);
        if (n != null) {
            if (n == ONE) {
                selectedOptions.remove(option);
            }
            else {
                int t = n.intValue();
                t--;
                selectedOptions.put(option, t);
            }
        }
        selectedOptionNames.remove(option.getName());
        fireOptionChange(option, false);
        if (selectedOptions != null && selectedOptions.isEmpty()) {
            selectedOptions = null;
            selectedOptionNames = null;
        }
    }

    public void addOptionsListener(EntryOptionsListener l) {
        if (optionsListeners == null) {
            optionsListeners = new ArrayList<EntryOptionsListener>();
        }
        optionsListeners.add(l);
    }

    public void removeOptionListener(EntryOptionsListener l) {
        if (optionsListeners == null || optionsListeners.isEmpty()) {
            return;
        }
        optionsListeners.remove(l);
        if (optionsListeners != null && optionsListeners.isEmpty()) {
            optionsListeners = null;
        }
    }

    public void addNumberListener(EntryNumberListener l) {
        if (numberListeners == null) {
            numberListeners = new ArrayList<EntryNumberListener>();
        }
        numberListeners.add(l);
    }

    public void removeNumberListener(EntryNumberListener l) {
        if (numberListeners == null || numberListeners.isEmpty()) {
            return;
        }
        numberListeners.remove(l);
        if (numberListeners != null && numberListeners.isEmpty()) {
            numberListeners = null;
        }
    }

    public void clearListeners() {
        numberListeners = null;
        optionsListeners = null;
    }

    private void fireNumberChange() {
        for (int i = 0; i < numberListeners.size(); i++) {
            EntryNumberListener l = numberListeners.get(i);
            l.fireNumberChanged(number);
        }
    }

    private void fireOptionChange(Option option, boolean added) {
        if (optionsListeners == null || optionsListeners.isEmpty()) {
            return;
        }
        for (int i = 0; i < optionsListeners.size(); i++) {
            EntryOptionsListener l = optionsListeners.get(i);
            l.fireOptionChanged(option, added);
        }
    }

    public int getOptionsCount(Option option) {
        if (selectedOptions == null || selectedOptions.isEmpty()) {
            return 0;
        }
        Integer n = selectedOptions.get(option);
        if (n == null) {
            return 0;
        }
        return n.intValue();
    }

    public void setOptionsCount(Option option, int count) {
        checkOptionHolders();
        Integer n = selectedOptions.get(option);
        selectedOptions.put(option, count);
        if (n == null) {
            fireOptionChange(option, true);
        }
        else {
            int t = n.intValue();
            if (t < count) {
                fireOptionChange(option, true);
            }
            else {
                fireOptionChange(option, false);
            }
        }
    }

    public int getCost() {
        if (cost < 0) {
            cost = calculateCost();
        }
        return cost;
    }

    public Unit getUnit() {
        return unit;
    }

    public Category getCategory() {
        return category;
    }

    public int getNumber() {
        return number;
    }

    public void setNumberAndUpdate(int number) {
        this.number = number;
        fireNumberChange();
    }

    public String getOptionsSummary() {
        return optionsSummary;
    }

    @Override
    public int compareTo(RosterEntry o) {
        return category.compareTo(o.category);
    }

    // <editor-fold defaultstate="collapsed" desc=" for serializer only ">
    /**
     * @deprecated this constructor is for serializer-only, must not be used in application code
     */
    @Deprecated
    public RosterEntry() {
    }

    /**
     * @deprecated this method is for serializer only
     */
    @Deprecated
    public void setCategory(Category category) {
        this.category = category;
    }

    /**
     * @deprecated this method is for serializer only
     */
    @Deprecated
    public void setNumber(int number) {
        this.number = number;
    }

    /**
     * @deprecated this method is for serializer only
     */
    @Deprecated
    public void setSelectedOptionNames(Set<String> selectedOptionNames) {
        this.selectedOptionNames = selectedOptionNames;
    }

    /**
     * @deprecated this method is for serializer only
     */
    @Deprecated
    public void setSelectedOptions(Map<Option, Integer> selectedOptions) {
        this.selectedOptions = selectedOptions;
    }

    /**
     * @deprecated this method is for serializer only
     */
    @Deprecated
    public void setUnit(Unit unit) {
        this.unit = unit;
    }

    /**
     * @deprecated this method is for serializer only
     */
    @Deprecated
    public Set<String> getSelectedOptionNames() {
        return selectedOptionNames;
    }

    /**
     * @deprecated this method is for serializer only
     */
    @Deprecated
    public Map<Option, Integer> getSelectedOptions() {
        return selectedOptions;
    }
    // </editor-fold>

}
